package sketch.ounit.fuzz;

import java.util.List;
//
//import org.eclipse.jdt.core.dom.Block;
//import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ExpressionStatement;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import org.eclipse.jdt.internal.core.dom.NaiveASTFlattener;

//import sketch.ast.ASTUtils;
import sketch.ounit.Values;
import sketch.util.Checker;
import sketch.util.Globals;

public class RandomFuzzInstantiator {

	public final MethodDeclaration method_declaration;
	public final List<MethodInvocation> random_fuzz_invokes;
	public final List<String> sequence_list;
	public final List<String> result_vars;
	
	//the resulting method declaration to return
	private String result = null;
	
	public RandomFuzzInstantiator(MethodDeclaration method_decl, List<MethodInvocation> invokes, List<String> seq_list, List<String> vars) {
		this.method_declaration = method_decl;
		this.random_fuzz_invokes = invokes;
		this.sequence_list = seq_list;
		this.result_vars = vars;
	}
	
	public String getResult() {
		if(result == null) {
			RandomFuzzInstantionVisitor visitor = new RandomFuzzInstantionVisitor(random_fuzz_invokes, sequence_list, result_vars);
			this.method_declaration.accept(visitor);
			result = visitor.getResult();
		}
		Checker.checkNull(result, "The result should not be null!");
		return result; 
	}
}

class RandomFuzzInstantionVisitor extends NaiveASTFlattener {
	public final List<MethodInvocation> random_fuzz_invokes;
	public final List<String> sequence_list;
	public final List<String> result_vars;
	public RandomFuzzInstantionVisitor(List<MethodInvocation> invokes, List<String> sequence_list, List<String> result_vars) {
		Checker.checkTrue(invokes.size() == sequence_list.size(), "The invoke size != sequence list size!, the invoke size: "
				+ invokes.size() + ", the sequence list size: " + sequence_list.size());
		Checker.checkTrue(invokes.size() == result_vars.size(), "The invoke size != sequence list size!, the invoke size: "
				+ invokes.size() + ", the result_vars size: " + result_vars.size());
		this.random_fuzz_invokes = invokes;
		this.sequence_list = sequence_list;
		this.result_vars = result_vars;
	}
	
	@Override
	//XXX a small limitation
	//you can not declare: A a = random(A), a1 = random(A);
	public boolean visit(VariableDeclarationStatement var_stmt) {
		//only check for 
		List<VariableDeclarationFragment> fragments = var_stmt.fragments();
		if(fragments.size() != 1) {
			return super.visit(var_stmt);
		}
		for(VariableDeclarationFragment fragment : fragments) {
			if(fragment.getInitializer() instanceof MethodInvocation) {
				MethodInvocation invoke = (MethodInvocation)fragment.getInitializer();
				int index = this.findIndex(invoke);
				if(index != -1) {
					String gen_seq = this.sequence_list.get(index);
					this.buffer.append(gen_seq);
					this.buffer.append(var_stmt.getType().toString() + " " + fragment.getName() + " = ");
					this.buffer.append(this.result_vars.get(index) + ";" + Globals.lineSep);
					return false;
				}
			}
		}
		return super.visit(var_stmt);
	}
	
	@Override
	public boolean visit(MethodInvocation method_invoke) {
		int index = this.findIndex(method_invoke);
		if(index != -1) {
			ASTNode node = method_invoke.getParent();
			if(node instanceof ExpressionStatement) {
			  String gen_seq = this.sequence_list.get(index);
			  this.buffer.append(gen_seq);
			} else if (node instanceof VariableDeclarationFragment) {
				//System.out.println(node.getParent().getClass());
			} else {
				Checker.fail("Unexpected type: " + node.getClass());
			}
			return false;
		}
		return super.visit(method_invoke);
	}
	
	private int findIndex(MethodInvocation invoke) {
		String invokeExpression = invoke.getExpression() == null ? "" : invoke.getExpression().toString();
		//String methodName = invoke.getName().toString();
		if((invokeExpression.equals(Values.random_fuzz_class) || invokeExpression.equals(Values.random_fuzz_full_class))) 
		{
			int index = -1;
			for(int i = 0; i < this.random_fuzz_invokes.size(); i++) {
				MethodInvocation method_invoke = this.random_fuzz_invokes.get(i);
				Checker.checkTrue(invoke.getAST() == method_invoke.getAST(), "The asts are not the same");
				if(invoke.getStartPosition() == method_invoke.getStartPosition()) {
					index = i;
					break;
				}
			}
			return index;
		} else {
			return -1;
		}
	}
}